#include <stdio.h>
#include <SDL.h>
#include "lvgl/lvgl.h"
#include "lv_examples/lv_examples.h"

SDL_Window *gWin = NULL;
SDL_Renderer *gRenderer = NULL;
SDL_Texture *gTexture = NULL;
static uint32_t tft_buf[LV_HOR_RES_MAX * LV_VER_RES_MAX];

typedef struct InputSDL {
    bool pressed;
    lv_coord_t x;
    lv_coord_t y;
} InputSDL_t;

InputSDL_t gInput = {false, 0, 0};

static void hal_init(void);
static int tick_thread(void *data);
static int lvtask_thread(void *data);
void my_disp_flush(lv_disp_t * disp, const lv_area_t * area, lv_color_t * color_p);
void my_disp_flush_ex(lv_disp_t * disp, const lv_area_t * area, lv_color_t * color_p);
void my_set_pixel(int32_t x, int32_t y, lv_color_t *color_p);
void my_set_rect(int32_t x1, int32_t y1, int32_t x2, int32_t y2, lv_color_t *color_p);
bool my_touchpad_read(lv_indev_drv_t * indev_driver, lv_indev_data_t * data);
bool my_touchpad_is_pressed(void);
void touchpad_get_xy(lv_coord_t *x, lv_coord_t *y);

int main(int argc, char* argv[]){

    //1,Call lv_init().
    lv_init();
    printf("lvgl init ok!\n");
    //2,Initialize your drivers.
    hal_init();
    printf("hal init ok!\n");
    //3,Register the display and input devices drivers in LittlevGL.
    /*Static or global buffer(s). The second buffer is optional*/
    static lv_disp_buf_t disp_buf;
    static lv_color_t buf_1[LV_HOR_RES_MAX * 100];
    static lv_color_t buf_2[LV_HOR_RES_MAX * 100];                   /*Declare a buffer for 100 lines*/
    lv_disp_buf_init(&disp_buf, buf_1, buf_2, LV_HOR_RES_MAX * 100);    /*Initialize the display buffer*/
    lv_disp_drv_t disp_drv;               /*Descriptor of a display driver*/
    lv_disp_drv_init(&disp_drv);          /*Basic initialization*/
    disp_drv.flush_cb = my_disp_flush_ex;    /*Set your driver function*/
    disp_drv.buffer = &disp_buf;          /*Assign the buffer to the display*/
    lv_disp_drv_register(&disp_drv);      /*Finally register the driver*/

    lv_indev_drv_t indev_drv;
    lv_indev_drv_init(&indev_drv);             /*Descriptor of a input device driver*/
    indev_drv.type = LV_INDEV_TYPE_POINTER;    /*Touch pad is a pointer-like device*/
    indev_drv.read_cb = my_touchpad_read;      /*Set your driver function*/
    lv_indev_drv_register(&indev_drv);         /*Finally register the driver*/
    printf("display and input device set ok!\n");

#if LV_USE_DEMO_WIDGETS
    lv_demo_widgets();
    printf("load demo widgets ok!\n");
#elif LV_USE_DEMO_PRINTER
    lv_demo_printer();
    printf("load demo printer ok!\n");
#elif LV_USE_DEMO_BENCHMARK
    lv_demo_benchmark();
    printf("load demo benchmark ok!\n");
#elif LV_USE_DEMO_STRESS
    lv_demo_stress();
    printf("load demo stress ok!\n");
#elif LV_USE_DEMO_KEYPAD_AND_ENCODER
    lv_demo_keypad_encoder();
    printf("load demo keypad and encoder ok!\n");
#endif

    bool quit = false;
    SDL_Event e;
    while(!quit){
        while(SDL_PollEvent(&e) != 0){
            switch(e.type){
                case SDL_QUIT:
                    quit = true;
                    break;
                case SDL_MOUSEBUTTONDOWN:
                    gInput.pressed = true;
                    gInput.x = e.motion.x;
                    gInput.y = e.motion.y;
                    printf("state= %d, x=%d, y=%d\n", e.button.state, e.motion.x, e.motion.y);
                    break;
                case SDL_MOUSEBUTTONUP:
                    gInput.pressed = false;
                    printf("state= %d, x=%d, y=%d\n", e.button.state, e.motion.x, e.motion.y);
                    break;
                case SDL_MOUSEMOTION:
                    gInput.x = e.motion.x;
                    gInput.y = e.motion.y;
                    //printf("mouse motion, x=%d, y=%d\n", e.motion.x, e.motion.y);
                    break;
            }
        }
    }
    printf("program quit!\n");
    SDL_DestroyWindow(gWin);
    SDL_DestroyRenderer(gRenderer);
    SDL_DestroyTexture(gTexture);
    SDL_Quit();

    return 0;
}

void hal_init(void){
    if(SDL_Init(SDL_INIT_VIDEO) != 0){
        printf("SDL init failed: %s\n", SDL_GetError());
        return;
    }else{
        if(SDL_CreateWindowAndRenderer(LV_HOR_RES_MAX, LV_VER_RES_MAX, SDL_WINDOW_OPENGL, &gWin, &gRenderer) != 0){
            printf("SDL create window and renderer failed: %s\n", SDL_GetError());
            return;
        }else{
            SDL_SetWindowTitle(gWin, "LittlevGL PC Simulator based on SDL2");
            gTexture = SDL_CreateTexture(gRenderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, LV_HOR_RES_MAX, LV_VER_RES_MAX);
            if(!gTexture){
                printf("create texture failed: %s\n", SDL_GetError());
            }else{
                SDL_SetTextureBlendMode(gTexture, SDL_BLENDMODE_BLEND);
                SDL_SetRenderDrawColor(gRenderer, 0xff, 0xff, 0xff, 0xff);
                SDL_RenderClear(gRenderer);
                SDL_RenderCopy(gRenderer, gTexture, NULL, NULL);
                SDL_RenderPresent(gRenderer);
            }
        }
    }

    SDL_Thread *tickThread = SDL_CreateThread(tick_thread, "tick", NULL);
    if(tickThread) printf("create tick thread ok!\n");
    else printf("create tick thread failed: %s\n", SDL_GetError());

    SDL_Thread *lvtaskThread = SDL_CreateThread(lvtask_thread, "lvtask", NULL);
    if(lvtaskThread) printf("create lvtask thread ok!\n");
    else printf("create lvtask thread failed: %s\n", SDL_GetError());
}

static int tick_thread(void *data)
{
    while(1) {
        lv_tick_inc(10);
        SDL_Delay(10);   /*Sleep for 1 millisecond*/
    }

    return 0;
}

static int lvtask_thread(void *data){
    while(1){
        lv_task_handler();
        SDL_Delay(5);
    }
}

void my_disp_flush(lv_disp_t * disp, const lv_area_t * area, lv_color_t * color_p){
    int32_t x, y;
    for(y = area->y1; y <= area->y2; y++) {
        for(x = area->x1; x <= area->x2; x++) {
            my_set_pixel(x, y, color_p);  /* Put a pixel to the display.*/
            color_p++;
        }
    }
    SDL_RenderPresent(gRenderer);
    lv_disp_flush_ready(disp);         /* Indicate you are ready with the flushing*/
}

void my_disp_flush_ex(lv_disp_t * disp, const lv_area_t * area, lv_color_t * color_p){
    int32_t x1 = area->x1;
    int32_t x2 = area->x2;
    int32_t y1 = area->y1;
    int32_t y2 = area->y2;

    if(x1 < 0 || x2 < 0 || y1 < 0 || y2 < 0 ||
       x1 > LV_HOR_RES_MAX - 1 || x2 > LV_HOR_RES_MAX - 1 ||
       y1 > LV_VER_RES_MAX - 1 || y2 > LV_VER_RES_MAX - 1){
        lv_disp_flush_ready(disp);
        return;
    }
    uint32_t w = x2 - x1 + 1;
    for(int32_t y = y1; y <= y2; y++) {
        memcpy(&tft_buf[y * LV_HOR_RES_MAX + x1], color_p, w * sizeof(lv_color_t));
        color_p += w;
    }

    SDL_UpdateTexture(gTexture, NULL, tft_buf, LV_HOR_RES_MAX * sizeof(uint32_t));
    SDL_RenderClear(gRenderer);
    SDL_RenderCopy(gRenderer, gTexture, NULL, NULL);
    SDL_RenderPresent(gRenderer);

    lv_disp_flush_ready(disp);         /* Indicate you are ready with the flushing*/
}

void my_set_pixel(int32_t x, int32_t y, lv_color_t *color_p){
    SDL_SetRenderDrawColor(gRenderer, color_p->ch.red, color_p->ch.green, color_p->ch.blue, color_p->ch.alpha);
    SDL_RenderDrawPoint(gRenderer, x, y);
}

bool my_touchpad_read(lv_indev_drv_t * indev_driver, lv_indev_data_t * data)
{
    data->state = my_touchpad_is_pressed() ? LV_INDEV_STATE_PR : LV_INDEV_STATE_REL;
    if(data->state == LV_INDEV_STATE_PR) touchpad_get_xy(&data->point.x, &data->point.y);

    return false; /*Return `false` because we are not buffering and no more data to read*/
}

bool my_touchpad_is_pressed(void){
    return gInput.pressed;
}

void touchpad_get_xy(lv_coord_t *x, lv_coord_t *y){
    *x = gInput.x;
    *y = gInput.y;
}
